package com.theaf.core.repo.base;

import com.theaf.core.annotation.Logic;
import com.theaf.core.constant.SymbolConstant;
import com.theaf.core.repo.kit.SimplePageBuilder;
import com.theaf.core.repo.kit.SimpleSortBuilder;
import com.theaf.core.repo.specification.SqlBuilder;
import com.theaf.core.repo.specification.SqlOperator;
import com.theaf.core.repo.specification.SqlValue;
import com.theaf.core.repo.specification.SubqueryEx;
import com.theaf.core.utils.BeanUtils;
import com.theaf.core.utils.LoginUtils;
import com.theaf.core.utils.SmExceptionHelper;
import com.theaf.core.utils.StringUtils;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.*;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.util.*;

/**
 * @Author:songwenke
 * @Description:Repository基类
 * @Date:create in 17:06 2017/12/12
 * @Modified By:
 **/
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T,ID>
        implements BaseRepository<T,ID> {

    private final EntityManager entityManager;

    //父类没有不带参数的构造方法，这里手动构造父类
    public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    /**
     * 基类保存，处理创建人时间及修改人时间
     * @param entity
     * @param <S>
     * @return
     */
    @Override
    @Transactional
    public <S extends T> S save(S entity) {
        try {
            ID id = null;
            for(Field field:entity.getClass().getDeclaredFields()){
                if (field.getAnnotation(Id.class) != null) {
                    id = (ID) BeanUtils.getProperty(entity, field.getName());
                    break;
                }
            }
            if(id==null||!this.exists(id)) {
                try {
                    BeanUtils.setProperty(entity, "creater", LoginUtils.getUserId() + SymbolConstant.COLON + LoginUtils.getUserName());
                } catch (Exception e) {}
                try {
                    BeanUtils.setProperty(entity, "createTime", new Timestamp(System.currentTimeMillis()));
                } catch (Exception e) {}
                try {
                    BeanUtils.setProperty(entity, "orderNo", ((Long) this.count()).intValue()+1);
                } catch (Exception e) {}
            }else{
                try {
                    BeanUtils.setProperty(entity, "updater", LoginUtils.getUserId() + SymbolConstant.COLON + LoginUtils.getUserName());
                } catch (Exception e) {}
                try {
                    BeanUtils.setProperty(entity, "updateTime",new Timestamp(System.currentTimeMillis()));
                } catch (Exception e) {}
            }
        }catch (Exception ee){}
        return super.save(entity);
    }

    //通过EntityManager来完成查询
    @Override
    public List<T> findCustom(Map<String,Object> term, Map<String,Boolean> order){
        return findAll(build(term), SimpleSortBuilder.generateSort(order));
    }

    @Override
    public Page<T> findCustom(Map<String,Object> term,Map<String,Boolean> order, int page,int size){
        return findAll(build(term), SimplePageBuilder.generate(page,size,SimpleSortBuilder.generateSort(order)));
    }
    @Override
    public Page<T> findCustom(Condition term){
        if(term==null){
            return null;
        }
        List<Sort.Order> orders = new ArrayList();
        if(term.getOrders()!=null) {
            for (String key : term.getOrders().keySet()) {
                orders.add(new Sort.Order((term.getOrders().get(key)? Sort.Direction.ASC:Sort.Direction.DESC), key));
            }
        }
        return findAll(build(term), SimplePageBuilder.generate(term.getPage(),term.getSize(), orders.size()==0?null:new Sort(orders)));
    }

    private Specification<T> build(Condition term){
        try {
            Map map = new BeanMap(term);
            SqlBuilder<T> build = new SqlBuilder<T>();
            for (String key : (Set<String>) map.keySet()) {
                if(key.equals("orders")||key.equals("page")||key.equals("size")||key.equals("class")){
                    continue;
                }
                Logic logic = term.getClass().getDeclaredField(key).getAnnotation(Logic.class);
                Object v = map.get(key);
                if(logic!=null) {
                    build.and(key, logic.value(), SqlValue.parse(v));
                }else{
                    build.and(key,SqlValue.parse(v));
                }
            }
            return build.gen();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    private Specification<T> build(Map<String,Object> term){
        SqlBuilder<T> build = new SqlBuilder<T>();
        for(String key:term.keySet()){
            String[] keys = key.split("_");
            if(keys.length==3){
                build.and(keys[2]+ SymbolConstant.LEFT_SMALL_BRACKET+keys[0]+SymbolConstant.RIGHT_SMALL_BRACKET,
                        SqlOperator.OPER.valueOf(keys[1].toUpperCase()),SqlValue.parse(term.get(key)));
            }else
            if(keys.length==1){
                build.and(keys[0],SqlValue.parse(term.get(key)));
            }else{
                build.and(keys[0], SqlOperator.OPER.valueOf(keys[1].toUpperCase()),SqlValue.parse(term.get(key)));
            }
        }
        return build.gen();
    }

    @Override
    public SubqueryEx subquery(SqlBuilder sqlBuilder){
        CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
        CriteriaQuery criteriaQuery = builder.createQuery(this.getDomainClass());
        Subquery subquery = criteriaQuery.subquery(this.getDomainClass());
        Root subRoot = subquery.from(this.getDomainClass());
        if(sqlBuilder.getSelects()==null|| sqlBuilder.getSelects().length==0){
            throw SmExceptionHelper.sysCheckException("selects必须设置！");
        };
        SubqueryEx subqueryEx = new SubqueryEx();
        subqueryEx.setSqlBuilder(sqlBuilder);
        subqueryEx.setSubquery(subquery.select(sqlBuilder.getJoin(subRoot, sqlBuilder.getSelects()[0])));
        return subqueryEx;
        /*return subquery.select(sqlBuilder.getJoin(subRoot, sqlBuilder.getSelects()[0])).
                                where(sqlBuilder.generatePredicate(subRoot,builder));*/
    }

    /**
     * 功能最强的查询方式
     * @param specification
     * @return
     */
    @Override
    public List query(Specification specification,int page,int size) {
        CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
        CriteriaQuery query = builder.createQuery(this.getDomainClass());
        Root root = query.from(this.getDomainClass());
        Predicate predicate = specification.toPredicate(root,query,builder);
        if (predicate != null) {
            query.where(predicate);
        }
        TypedQuery typedQuery = entityManager.createQuery(query);
        if(size!=0) {
            typedQuery.setFirstResult((page-1)*size);
            typedQuery.setMaxResults(size);
        }
        return applyRepositoryMethodMetadata(typedQuery).getResultList();
    }

    @Override
    public List query(Specification specification) {
        return query(specification,0,0);
    }

    /**
     * 初始化级别编码
     * @param t 对象
     * @param levelCodeField 级别编码字段
     * @param idField 主键字段
     * @param parentField 父ID字段
     */
    @Override
    @Transactional
    public void initLevel(T t, String levelCodeField, String idField, String parentField) {
        ID id = null;
        String levelCode = "";
        boolean added = false;
        try {
            id = (ID) PropertyUtils.getProperty(t, idField);
        }catch (Exception e) {
            throw SmExceptionHelper.sysCheckException("idField对应字段名不存在");
        }
        if(id==null||!exists(id)){
            added = true;
        }
        if(t != null){
            try {
                ID tempid = null;
                try {
                    tempid = (ID) PropertyUtils.getNestedProperty(t, parentField);
                }catch(Exception e2){}
                final ID parentId = tempid;
                final ID cId = id;
                List ps = query(new Specification(){
                    @Override
                    public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                        SqlBuilder p = null;
                        if(parentId==null){
                            p = SqlBuilder.start(parentField, SqlOperator.OPER.NULL,null)
                                    .and(idField, SqlOperator.OPER.NE,cId);
                        }else{
                            p = SqlBuilder.start(parentField,parentId)
                                    .and(idField, SqlOperator.OPER.NE,cId);
                        }
                        criteriaQuery.select(criteriaBuilder.max(root.get(levelCodeField)).as(String.class));
                        return p.gen().toPredicate(root,criteriaQuery,criteriaBuilder);
                    }
                });
                String childMaxLevelCode = (String)ps.get(0);
                if(StringUtils.isEmpty(childMaxLevelCode)){
                    if(parentId==null){
                        levelCode = from10To62(1);
                    }else {
                        T p = getOne(parentId);
                        if(p == null){
                            levelCode = from10To62( 1);
                        }else {
                            levelCode = BeanUtils.getProperty(p, levelCodeField) + from10To62(1);
                        }
                    }
                }else {
                    if(!added) {
                        String oldlEvelCode = (String) PropertyUtils.getProperty(t, levelCodeField);
                        oldlEvelCode = oldlEvelCode.substring(0, oldlEvelCode.length() - 4);
                        if (childMaxLevelCode.indexOf(oldlEvelCode) == 0&&
                                childMaxLevelCode.length()==oldlEvelCode.length()+4) {
                            return;
                        }
                    }
                    levelCode = childMaxLevelCode.substring(0,childMaxLevelCode.length()-4)+
                            addOne(childMaxLevelCode.substring(childMaxLevelCode.length()-4));
                }
                BeanUtils.setProperty(t,levelCodeField,levelCode);
                save(t);
            } catch (Exception e) {
                e.printStackTrace();
                throw SmExceptionHelper.sysCheckException("levelCodeField,idField,parentField对应字段名不存在");
            }
        }
        //只有修改时才处理下级
        if(!added) {
            List<T> ts = findAll(SqlBuilder.start(parentField, id).gen());
            int index = 0;
            for (T tt : ts) {
                try {
                    BeanUtils.setProperty(tt, levelCodeField, levelCode + from10To62(index++));
                    initLevel(tt, levelCodeField, idField, parentField);
                } catch (Exception e) {
                    throw SmExceptionHelper.sysCheckException("levelCodeField对应字段名不存在");
                }
            }
            save(ts);
        }
    }

    @Override
    public boolean judgeChange(ID id, String field, Object value) {
        try {
            Object src = BeanUtils.getProperty(this.getOne(id),field);
            if(src==null&&value==null) {
                return false;
            }
            if((src==null&&value!=null)||(src!=null&&value==null)) {
                return true;
            }
            return !src.equals(value);
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    @Transactional
    public int updateCustom(Map<String,Object> set, SqlBuilder specification){
        return updateCustom(set,specification,this.getDomainClass());
    }

    @Override
    @Transactional
    public int updateCustom(Map<String,Object> set, SqlBuilder specification, Class entityClass) {
        CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
        CriteriaUpdate criteriaUpdate = builder.createCriteriaUpdate(entityClass);
        Root root = criteriaUpdate.from(entityClass);
        Predicate predicate = specification.generatePredicate(root,builder);
        if (predicate != null) {
            criteriaUpdate.where(predicate);
        }
        set.entrySet().parallelStream().map(n->criteriaUpdate.set(root.get(n.getKey()),n.getValue()));
        try{
            if(root.getModel().getDeclaredAttribute("updater")!=null){
                criteriaUpdate.set(root.get("updater"),LoginUtils.getUserId() + SymbolConstant.COLON + LoginUtils.getUserName());
            }
        }catch (Exception e){}
        try{
            if(root.getModel().getDeclaredAttribute("updateTime")!=null){
                criteriaUpdate.set(root.get("updateTime"),new Timestamp(System.currentTimeMillis()));
            }
        }catch (Exception e){}
        int i = entityManager.createQuery(criteriaUpdate).executeUpdate();
        SqlOperator.clearRootCache();
        return i;
    }

    @Override
    @Transactional
    public int deleteCustom(SqlBuilder specification) {
        CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
        CriteriaQuery criteriaQuery = builder.createQuery(this.getDomainClass());
        Root root = criteriaQuery.from(this.getDomainClass());
        Predicate predicate = specification.generatePredicate(root,builder);
        if (predicate != null) {
            criteriaQuery.where(predicate);
        }
        SqlOperator.clearRootCache();
        List list = entityManager.createQuery(criteriaQuery).getResultList();
        list.forEach(n->this.delete((T)n));
        return list.size();
    }

    private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) {
        if (this.getRepositoryMethodMetadata() == null) {
            return query;
        } else {
            LockModeType type = this.getRepositoryMethodMetadata().getLockModeType();
            TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type);
            this.applyQueryHints(toReturn);
            return toReturn;
        }
    }

    private void applyQueryHints(Query query) {
        Iterator var2 = this.getQueryHints().entrySet().iterator();
        while(var2.hasNext()) {
            Map.Entry<String, Object> hint = (Map.Entry)var2.next();
            query.setHint((String)hint.getKey(), hint.getValue());
        }
    }

    @Override
    public T findByIdNewIfisNull(ID id){
        if(id==null) {
            try {
                return getDomainClass().newInstance();
            } catch (Exception e) {
                throw SmExceptionHelper.sysCheckException(getDomainClass()+"无空参数构造方法");
            }
        }else{
            T entity = findOne(id);
            if(entity==null){
                try {
                    return getDomainClass().newInstance();
                } catch (Exception e) {
                    throw SmExceptionHelper.sysCheckException(getDomainClass()+"无空参数构造方法");
                }
            }
            return entity;
        }
    }

    private static char[] array = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            .toCharArray();

    //10进制转为62进制
    private static String from10To62(long number) {
        final int N = 62;
        Long rest = number;
        Stack<Character> stack = new Stack<Character>();
        StringBuilder result = new StringBuilder(0);
        while (rest != 0) {
            stack.add(array[new Long((rest % N)).intValue()]);
            rest = rest / N;
        }
        for (; !stack.isEmpty();) {
            result.append(stack.pop());
        }
        String r = result.length() == 0 ? "0":result.toString();
        int len = r.length();
        for(int i=4;i>len;i--){
            r = "0"+r;
        }
        return r;
    }

    private static long from62To10(String st){
        long l = 0;
        int w = 0;
        for(int i=st.length()-1;i>=0;i--){
            char c = st.charAt(i);
            int index = -1;
            for(int j=0;j<array.length;j++){
                if (array[j] == c) {
                    index = j;
                }
            }
            l+=index*Math.pow(62,w++);
        }
        return l;
    }

    private static String addOne(String v62){
        return from10To62(from62To10(v62)+1);
    }

    public static void main(String[] args){
        long l = 0;
        for(int i=0;i<100;i++) {
            String s = from10To62(i);
            System.out.println(i+"="+s+"="+from62To10(s)+"="+addOne(s));
        }
    }
}
